package com.vivi.codereview.actors;

import java.io.File;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import com.vivi.codereview.WhatTheFuckException;
import com.vivi.codereview.tools.ClearCaseHandler;
import com.vivi.codereview.tools.Log;
import com.vivi.codereview.tools.SelectRangeHandler;
import com.vivi.codereview.tools.SystemHandler;
import com.vivi.codereview.tools.UserInputHandler;
import com.vivi.codereview.tools.ZipHandler;

/**
 * This class is the abstract base for all Actors we currently have. It contains
 * some methods to be used by any other actor and contains all the required
 * handlers to call any specific process (zip, clearcase...).
 *
 * @author vivi
 *
 */
public abstract class BaseActor implements IActor {

  /**
   * Max number of entries for user before raising an exception stopping the
   * program (in case entered value is not correct)
   */
  public static final int MAX_NB_ATTEMPTS_TO_ENTER_VALUE = 5;

  /**
   * name of the created zip file.
   */
  protected static final String ZIP_FILENAME = "codeReviewZip.zip";

  /**
   * name of folder into review folder where zip file is stored.
   */
  protected static final String SUB_ZIP_FOLDER = "zip";

  /**
   * name of the Log.log file to be included at root level of the zip file.
   */
  protected static final String ZIP_LOG_FILENAME = "codeReview.Log";

  /**
   * Array of locations to check if we have a not empty jdevcc folder,
   * following the given ordering.
   */
  protected static final String[] JDEVCC_LOCATIONS = new String[] { "G:\\jdevcc", "D:\\jdevcc" };

  /**
   * Command to find modified files.
   */
  protected static final String MODIFIED_FILES_COMMAND = "dir /a-r-d /s /w /b";

  /**
   * Remote path where review projects are stored.
   */
  protected static final String REVIEW_FOLDER = "L:\\R&D\\Common\\HRF-NCM\\HOT\\codereview\\AgileReviews";

  /**
   * Buffer for System.in system
   */
  public static final int BUFFER_SYSTEM_IN = 1;

  private static final String JUNIT_REGEXP = "^Test.*.java$";

  /**
   * Handler for Zip related processes.
   */
  private ZipHandler zipUtil;

  /**
   * Handler for ClearCase related processes.
   */
  private ClearCaseHandler ccUtil;

  /**
   * Handler for system related processes.
   */
  private SystemHandler sysUtil;

  /**
   * Handler for user input processes.
   */
  private UserInputHandler inputUtil;

  /**
   * Handler for select range processes.
   */
  private SelectRangeHandler selectRangeUtil;

  /**
   * Base constructor, instanciate all the handlers.
   */
  public BaseActor() {
    this.selectRangeUtil = new SelectRangeHandler();
    this.zipUtil = new ZipHandler(ZIP_FILENAME, ZIP_LOG_FILENAME);
    this.ccUtil = new ClearCaseHandler();
    this.sysUtil = new SystemHandler();
    this.inputUtil = new UserInputHandler();
  }

  @Override
  public abstract int process() throws Exception;

  /**
   * We try to find the folder with jdevcc folder. An exception is thrown
   * blocking all the flow if no root directory has been found.
   *
   * @return
   */
  protected String getRootDirectory() throws Exception {
    List<String> rootDirectories = Arrays.asList(JDEVCC_LOCATIONS);
    Log.log("we look between those location for jdevcc: " + rootDirectories.toString(), Log.D);
    for (String root : rootDirectories) {
      File file = new File(root);
      if (file != null && file.exists() && file.isDirectory() && file.listFiles() != null
          && file.listFiles().length > 0) {
        // Since we find an existing jdevcc folder not empty, we
        // consider it is the right one.
        Log.log("We use this jdevcc location " + file.getAbsolutePath(), Log.D);
        return file.getAbsolutePath();
      }
      else {
        Log.log("This root folder is not valid " + file, Log.D);
      }
    }
    Log.log("We didn't find root folder between " + rootDirectories.toString(), Log.E);
    throw new WhatTheFuckException(Log.Exit.NO_JDEVCC_FOLDER);
  }

  /**
   * We list all sub-folders and ask user which one he wants to select.
   *
   * @param mainFolder
   *          the absolute path of folder we look into
   * @return the absolute path of the selected folder
   * @throws Exception
   */
  protected String selectFolder(String mainFolder, String label) throws Exception {
    List<File> folderContent = Arrays.asList(new File(mainFolder).listFiles());
    Log.log("We have this folder content " + folderContent.toString(), Log.D);
    // Map with canonical name, for display, and absolute name, for code
    Map<String, String> folders = new TreeMap<String, String>();
    for (File folder : folderContent) {
      if (folder.isDirectory()) {
        folders.put(folder.getCanonicalPath(), folder.getAbsolutePath());
        Log.log(folder + " is kept", Log.D);
      }
      else {
        Log.log(folder + " is not a directory.", Log.D);
      }
    }
    String shortFolderName = new UserInputHandler().letUserChooseOne(folders.keySet(), label, true);
    if (shortFolderName == null) {
      Log.log("You didn't select any right value. We cannot process.", Log.E);
      throw new WhatTheFuckException(Log.Exit.REQUIRED_USER_ENTRY);
    }
    return folders.get(shortFolderName);
  }

  /**
   * Check if we have to create a zip, if we have at least one file to zip. At least one Junit should be present as
   * well.
   *
   * @param files
   *          the Set of files we want to zip, can be empty
   * @return
   */
  protected boolean checkToBeZipped(Set<String> files) throws Exception {
    int size = files.size();
    Log.log("We want to zip " + size + " files", Log.D);
    if (size == 0) {
      Log.log("We won't do any zip since no file has to be zipped", Log.W);
    }
    return files.size() > 0;
  }

  /**
   * Check in a given set of files if at least one of them is a JUnit.
   *
   * @param files
   *          set of files to consider
   * @return true if at least one file is a junit, false otherwise
   */
  protected boolean checkJunitInFiles(Set<String> files) {
    Log.log("we look for at least one Junit in list of files to be zipped", Log.D);
    boolean junitFound = false;
    if (files != null) {
      Iterator<String> itFile = files.iterator();
      while (itFile.hasNext() && !junitFound) {
        junitFound = checkJunitInFile(itFile.next());
        if (junitFound) {
          break;
        }
      }
    }
    Log.log("Does a Junit at least has been found: " + junitFound, Log.D);
    return junitFound;
  }

  /**
   * Check if a given file is a junit. It checks the name starts with "Test". Maybe better way to detect junit is to
   * look into file the imports to see if a junit is found.
   *
   * @param file
   * @return
   */
  protected boolean checkJunitInFile(String file) {
    Log.log("We check if following file is a junit: " + file, Log.D);
    String filename = new File(file).getName();
    boolean isJunit = filename.matches(JUNIT_REGEXP);
    Log.log("JUnit check done for " + file + ": " + isJunit, Log.D);
    return isJunit;
  }

  /**
   * Merge 2 Maps together, putting values of the second one into the first one. Values are Set.
   *
   * @param origin
   *          the Map we want to merge into
   * @param add
   *          the Map we want to add
   */
  protected <K, V> void mergeMaps(Map<K, Set<V>> origin, Map<K, Set<V>> add) {
    for (Map.Entry<K, Set<V>> entry : add.entrySet()) {
      if (origin.containsKey(entry.getKey())) {
        origin.get(entry.getKey()).addAll(entry.getValue());
      }
      else {
        Set<V> newSet = new TreeSet<V>();
        newSet.addAll(entry.getValue());
        origin.put(entry.getKey(), newSet);
      }
    }
  }

  /**
   * Get the Zip handler.
   *
   * @return zip handler
   */
  protected ZipHandler getZipUtil() {
    return this.zipUtil;
  }

  /**
   * Get the ClearCase handler.
   *
   * @return the ccUtil
   */
  protected ClearCaseHandler getCcUtil() {
    return ccUtil;
  }

  /**
   * Get the Select Range handler.
   *
   * @return the selectRangeUtil
   */
  public SelectRangeHandler getSelectRangeUtil() {
    return selectRangeUtil;
  }

  /**
   * Get the system handler.
   *
   * @return the sysUtil
   */
  protected SystemHandler getSysUtil() {
    return sysUtil;
  }

  /**
   * Get the user input handler.
   *
   * @return the inputUtil
   */
  protected UserInputHandler getInputUtil() {
    return inputUtil;
  }
}
